package org.webdb.webapp;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.codehaus.jackson.map.ObjectMapper;
import org.picocontainer.web.PicoScopedContainers;
import org.picocontainer.web.ScopedContainers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.webdb.core.UnsupportedDatabaseTypeException;
import org.webdb.core.config.ConfigProcessor;
import org.webdb.core.config.ConfigProcessor.ResourceResolver;
import org.webdb.core.config.DatabaseConfig;
import org.webdb.core.database.DataBase;
import org.webdb.core.database.DataBaseRegistry;

public class Application implements ServletContextListener {
    private static final String DATABASE_BASE_PATH = "/WEB-INF/databases";
    private static final Logger logger = LoggerFactory
            .getLogger("Webdb Application");
    private final Map<String, String> supported = new LinkedHashMap<String, String>();

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }

    @Override
    public void contextInitialized(final ServletContextEvent sce) {
        try {
            loadTypes(sce.getServletContext());
            PicoScopedContainers psc = new PicoScopedContainers(
                    (ScopedContainers) sce.getServletContext().getAttribute(
                            ScopedContainers.class.getName()));

            DataBaseRegistry databaseRegistry = (DataBaseRegistry) psc
                    .getApplicationContainer().getComponent("databaseRegistry");

            String path = sce.getServletContext().getRealPath(
                    DATABASE_BASE_PATH);

            File directory = new File(path);
            List<File> files = new LinkedList<File>();
            final Deque<File> directories = new LinkedList<File>();
            directories.add(directory);
            while (!directories.isEmpty()) {
                directory = directories.poll();
                logger.info("Looking in {} ...", directory.getAbsolutePath()
                        .substring(path.length() - DATABASE_BASE_PATH.length()));
                if (directory.isDirectory()) {
                    files.addAll(Arrays.asList(directory
                            .listFiles(new FileFilter() {
                                @Override
                                public boolean accept(File pathname) {

                                    boolean isFile = pathname.isFile()
                                            && pathname.getName().endsWith(
                                                    ".json")
                                            && pathname.canRead();

                                    if (!isFile && pathname.isDirectory())
                                        directories.add(pathname);
                                    return isFile;
                                }
                            })));
                }
            }
            ObjectMapper mapper = new ObjectMapper();

            for (File file : files) {
                logger.info("Loading {} ...", file.getName());
                try {
                    DatabaseConfig config = mapper.readValue(file,
                            DatabaseConfig.class);
                    ConfigProcessor processor = getProcessor(config.getType());

                    DataBase database = processor.proccess(config,
                            new ResourceResolver() {

                                @Override
                                public InputStream getStream(String resource)
                                        throws IOException {
                                    String filePath = sce.getServletContext()
                                            .getRealPath(
                                                    "/WEB-INF/"
                                                            .concat(resource));
                                    File file = new File(filePath);
                                    if (file.exists() && file.canRead())
                                        return new FileInputStream(file);

                                    throw new FileNotFoundException("/WEB-INF/"
                                            .concat(resource));
                                }

                            });

                    databaseRegistry.put(database);
                    logger.info("Loaded {}!", file.getName());

                } catch (Throwable e) {
                    logger.warn("Fail " + file.getName() + "!", e);
                }
            }
        } catch (Exception e1) {
            throw new ApplicationError(e1);
        }
    }

    private void loadTypes(ServletContext servletContext) throws IOException {
        File file = new File(
                servletContext
                        .getRealPath("/WEB-INF/supportedTypes.properties"));
        Properties properties = new Properties();
        properties.load(new FileInputStream(file));

        for (Entry<Object, Object> entry : properties.entrySet())
            supported.put(entry.getKey().toString(), entry.getValue()
                    .toString());
    }

    @SuppressWarnings("unchecked")
    private ConfigProcessor getProcessor(String type)
            throws ClassNotFoundException {
        String className = supported.get(type);
        if (className != null) {
            Class<ConfigProcessor> clazz = (Class<ConfigProcessor>) this
                    .getClass().getClassLoader().loadClass(className);

            try {
                return clazz.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        throw new UnsupportedDatabaseTypeException(type);
    }

}
